package me.zhengjie.modules.system.rest;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.exception.BizException;
import me.zhengjie.modules.pay.domain.constant.WXPayConstants;
import me.zhengjie.modules.biz.domain.WxImgCode;
import me.zhengjie.modules.biz.mapper.WxImgCodeMapper;
import me.zhengjie.modules.file.util.IdUtils;
import me.zhengjie.modules.security.config.bean.LoginProperties;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.modules.security.security.TokenProvider;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.modules.security.service.dto.AuthorityDto;
import me.zhengjie.modules.security.service.dto.JwtUserDto;
import me.zhengjie.modules.system.domain.Dept;
import me.zhengjie.modules.system.domain.User;
import me.zhengjie.modules.system.domain.WxUser;
import me.zhengjie.modules.system.domain.req.WxLoginReq;
import me.zhengjie.modules.system.domain.resp.WxLoginResp;
import me.zhengjie.modules.system.mapper.DeptMapper;
import me.zhengjie.modules.system.mapper.UserMapper;
import me.zhengjie.modules.system.mapper.WxUserMapper;
import me.zhengjie.utils.RedisUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.util.ArrayList;


import static me.zhengjie.modules.base.utils.ConvertCardUtil.codeToImg;
import static me.zhengjie.modules.biz.constant.GlobalConstant.*;
import static me.zhengjie.modules.biz.domain.enums.ErrorCode.*;

@RestController
@RequestMapping("/wx")
@Slf4j
@ApiOperation("微信小程序登陆相关接口")
@RequiredArgsConstructor
public class WxLogin {

    private final RestTemplate restTemplate;
    private final SecurityProperties properties;
    private final RedisUtils redisUtils;
    private final OnlineUserService onlineUserService;
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final PasswordEncoder passwordEncoder;
    private final DeptMapper deptMapper;
    private final WxImgCodeMapper wxImgCodeMapper;
    private final WxUserMapper wxUserMapper;
    private final UserMapper userMapper;

    @Value(value = "${url.default}")
    private String fileUrl;

    @Value("${url.card}")
    private String serverCard;

    @Value("${url.cardUrl}")
    private String cardUrl;


    @AnonymousAccess
    @PostMapping("/login")
    @Transactional
    public ResponseEntity<Object> wxLogin(@Validated @RequestBody WxLoginReq req,HttpServletRequest httpServletRequest) throws Exception {
        log.info("微信登陆：{}",req);
        // 获取access_token
        String getAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?" +
                "appid=" + WXPayConstants.APP_ID +
                "&secret=" + WXPayConstants.AppSecret +
                "&grant_type=client_credential";
        String accessTokenJson = restTemplate.getForObject(getAccessTokenUrl, String.class);
        JSONObject accessTokenObject = JSONObject.parseObject(accessTokenJson);
        String accessToken = accessTokenObject.getString("access_token");

        // 构建请求体
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        JSONObject json = new JSONObject();
        json.put("code", req.getCode());
        HttpEntity<String> request = new HttpEntity<>(json.toString(), headers);
        // 发送post请求，获取微信用户的手机号
        String getPhoneUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" +
                accessToken;
        JSONObject phoneJson = restTemplate.postForEntity(getPhoneUrl,request, JSONObject.class).getBody();
        // 解析json中的手机号
        JSONObject jsonObject = new JSONObject(phoneJson);
        JSONObject phoneInfo = jsonObject.getJSONObject("phone_info");
        if(!StringUtils.contains(phoneJson.toString(),"ok")){
            throw new BizException(GET_PHONE_FAIL.getCode(),GET_PHONE_FAIL.getMessage());
        }
        // 获取用户手机号
        String phoneNumber = phoneInfo.getString("purePhoneNumber");
        if(phoneNumber == null){
            throw new BizException(GET_PHONE_FAIL.getCode(),GET_PHONE_FAIL.getMessage());
        }
        // 获取/添加微信用户信息
        WxUser wxUser = retrieveOrAddUser(phoneNumber,req);
        // 获取/添加用户表信息（app用户获取授权需要使用这里的用户信息）
        User user = addAdminUser(wxUser);
        // 生成token
        String token = generateAndStoreToken(user, httpServletRequest);
        // 封装返回对象
        WxLoginResp resp = new WxLoginResp();
        resp.setToken(token);
        resp.setWxUser(wxUser);
        return ResponseEntity.ok(resp);
    }

    private User addAdminUser(WxUser wxUser) {
        // 2.针对授权的逻辑,这里的用户仅仅只需保存用户，密码
        QueryWrapper<User> userWrapper = new QueryWrapper<>();
        userWrapper.eq("phone", wxUser.getPhone());
        // 如果手机号为空，则说明用户不存在，也需要添加
        User user = userMapper.selectOne(userWrapper);
        if (user == null) {
            // 将微信用户信息保存到本地表
            user = new User();
            // 初始化用户,用户表中的 username和nickName不能出现中文！！！
            user.setUsername(IdUtils.generateRandomId(32));
            user.setNickName(wxUser.getNickName());
            // 默认密码 123456
            user.setPassword(passwordEncoder.encode("123456"));
            user.setPhone(wxUser.getPhone());
            // 微信登陆用户状态设置为1
            user.setType(1);
            // 部门：学生部门
            user.setDeptId(18L);
            // 普通用户
            user.setIsAdmin(false);
            // 默认启用
            user.setEnabled(true);
            // 邮箱为手机号＋qq.com,后台用户表中随便写
            user.setEmail(wxUser.getPhone() + "@qq.com");
            userMapper.insert(user);
        }
        // 搜索用户所在部门，并进行封装
        Dept dept = deptMapper.selectOne(new QueryWrapper<Dept>().eq("dept_id", user.getDeptId()));
        user.setDept(dept);
        ArrayList<Long> dataScopes = new ArrayList<>();
        ArrayList<AuthorityDto> authorities = new ArrayList<>();

        JwtUserDto jwtUserDto = new JwtUserDto(user, dataScopes, authorities);
        redisUtils.set(LoginProperties.cacheKey + user.getUsername(),jwtUserDto);
        return user;
    }

    @PostMapping("/getOpenId")
    public ResponseEntity<String> getOpenId(@Validated @RequestBody WxLoginReq req){
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + WXPayConstants.APP_ID +
                "&secret=" + WXPayConstants.AppSecret +
                "&js_code=" + req.getCode() +
                "&grant_type=authorization_code";
        String jsonData = restTemplate.getForObject(url, String.class);
        JSONObject jsonObject = JSONObject.parseObject(jsonData);
        if (StringUtils.contains(jsonData, "errcode")) {
            log.info("获取失败");
        }
        // 获取openId和sessionKey
        String openid = jsonObject.getString("openid");
        String sessionKey = jsonObject.getString("session_key");
        // 保存用户信息
        saveOpenIdByPhone(openid, sessionKey, req.getPhoneNumber());
        return ResponseEntity.ok(openid);
    }

    private void saveOpenIdByPhone(String openid, String sessionKey, String phoneNumber) {
        if (StringUtils.isBlank(openid) || StringUtils.isBlank(sessionKey) || StringUtils.isBlank(phoneNumber)) {
            log.error("参数不能为空");
            return;
        }
        QueryWrapper<WxUser> wrapper = new QueryWrapper<>();
        wrapper.eq("phone", phoneNumber);
        WxUser wxUser = wxUserMapper.selectOne(wrapper);
        if (wxUser == null) {
            log.info("用户不存在");
            return;
        }
        // 更新用户信息
        wxUser.setOpenId(openid);
        wxUser.setSessionkey(sessionKey);
        wxUser.setPhone(phoneNumber);
        wxUser.setCreateTime(LocalDateTime.now());
        UpdateWrapper<WxUser> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("phone", phoneNumber);
        wxUserMapper.update(wxUser, updateWrapper);
    }

    // 从数据库中获取用户信息，如果用户不存在则新增用户
    private WxUser retrieveOrAddUser(String phoneNumber,WxLoginReq req) throws UnsupportedEncodingException {
        // 1.对微信用户表的逻辑
        QueryWrapper<WxUser> wrapper = new QueryWrapper<>();
        wrapper.select("phone","id","open_id","is_vip","is_tz_vip","is_zsb_vip").eq("phone", phoneNumber);
        WxUser wxUser = wxUserMapper.selectOne(wrapper);
        if (wxUser == null) {
            // 将微信用户信息保存到本地表
            wxUser = new WxUser();
            wxUser.setNickName(WX_USER + IdUtils.generateRandomId(16));
            wxUser.setAvatarUrl(fileUrl + DEFAULT_AVATAR);
            wxUser.setPhone(phoneNumber);
            // 设置用户的imgCode和imgCodeUrl
            wxUser.setImgCode(IdUtils.generateRandomId(16));
            String imgCodeUrl = codeToImg(wxUser.getImgCode(),serverCard,cardUrl);
            wxUser.setImgCodeUrl(imgCodeUrl);
            wxUserMapper.insert(wxUser);

            // 如果是通过扫描二维码进入的用户，保存邀请关系
            if(req.getImgCode() != null){
                WxImgCode wxImgCode = new WxImgCode();
                WxUser recommendUser = wxUserMapper.selectOne(new QueryWrapper<WxUser>().eq("img_code", req.getImgCode()));
                wxImgCode.setParentId(Long.valueOf(recommendUser.getId()));
                wxImgCode.setId(wxUser.getId().longValue());
                wxImgCodeMapper.insert(wxImgCode);
            }
        }
        return wxUser;
    }

    /** 生成 token 并存储到 Redis 中 */
    private String generateAndStoreToken(User user, HttpServletRequest request) {
        // username不能出现中文，学生用户的密码默认为123456
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(user.getUsername(),"123456");
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token,request);
        return properties.getTokenStartWith() + token;
    }

}
